﻿using Quartz;
using Quartz.Impl;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Web;
using TaskManagementSystem.Middleware;

namespace TaskManagementSystem.ScheduledTask
{
    internal class ScheduledTaskCore
    {
        private Data.DbContext dbContext = new Data.DbContext();
        private IScheduler _Scheduler = null;

        /// <summary>
        /// 定时任务调度程序
        /// </summary>
        private IScheduler Scheduler
        {
            get
            {
                if (_Scheduler == null)
                {
                    NameValueCollection properties = new NameValueCollection();
                    //调度器属性
                    properties["quartz.scheduler.instanceName"] = "WeiXinScheduler";//实例名
                    properties["quartz.scheduler.instanceId"] = "WeiXinScheduler";  //实例ID

                    //线程池属性
                    properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz";
                    properties["quartz.threadPool.threadCount"] = "10";//工作线程数     
                    properties["quartz.threadPool.threadPriority"] = "Normal";//工作线程的优先级

                    //作业存储设置
                    properties["quartz.jobStore.misfireThreshold"] = "60000";
                    properties["quartz.jobStore.type"] = "Quartz.Simpl.RAMJobStore, Quartz";

                    #region 远程调用配置
                    //远程调用配置（预留备用）
                    //properties["quartz.scheduler.exporter.type"] = "Quartz.Simpl.RemotingSchedulerExporter, Quartz";
                    //properties["quartz.scheduler.exporter.port"] = Config.Port;
                    //properties["quartz.scheduler.exporter.bindName"] = "QuartzScheduler";
                    //properties["quartz.scheduler.exporter.channelType"] = "tcp";
                    #endregion

                    #region 以下配置使用AdoJobStore，性能没有RAMJobStore高，且因多服务器问题可能导致各种数据冲突，现已暂时放弃，留待以后做集群

                    //properties["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz";
                    //properties["quartz.jobStore.useProperties"] = "true";
                    //properties["quartz.jobStore.dataSource"] = "default";
                    //properties["quartz.jobStore.tablePrefix"] = "QRTZ_";
                    //properties["quartz.jobStore.clustered"] = "false";
                    //properties["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz";

                    //properties["quartz.dataSource.default.connectionString"] = WebConfiguration.DataBaseSetting.GetConnectionString(string.Empty);
                    //properties["quartz.dataSource.default.provider"] = "SqlServer-20";

                    #endregion

                    ISchedulerFactory sf = new StdSchedulerFactory(properties);
                    _Scheduler = sf.GetScheduler();
                }
                return _Scheduler;
            }
        }

        internal static ScheduledTaskCore CurrentScheduledTask
        {
            get
            {
                return new ScheduledTaskCore();
            }
        }

        /// <summary>
        /// 设置任务触发器
        /// </summary>
        /// <param name="job"></param>
        /// <returns></returns>
        private ITrigger SetJobTrigger(Entity.SystemCronConfigData data)
        {
            TriggerKey tk = GetTriggerKey(data.JobKey, data.GroupId.ToString());
            TriggerBuilder triggerBuilder = TriggerBuilder.Create().WithIdentity(tk);

            if (!data.Immediately && data.NextTime.CompareTo(DateTime.Now) > 0)
            {
                triggerBuilder = triggerBuilder.StartAt(data.NextTime);
            }
            else
            {
                data.Immediately = true;
                data.NextTime = DateTime.Now;
                dbContext.Entry(data).State = System.Data.Entity.EntityState.Modified;
                dbContext.SaveChanges();

                triggerBuilder = triggerBuilder.StartNow();
            }
            TimeSpan ts;
            switch ((Entity.Enum.JobType)data.JobType)
            {
                case Entity.Enum.JobType.Time:
                    //作业只执行一次
                    if (data.Immediately) triggerBuilder = triggerBuilder.WithSimpleSchedule(x => x.WithMisfireHandlingInstructionFireNow());
                    break;
                case Entity.Enum.JobType.Seconds:
                    //作业每...秒执行一次
                    triggerBuilder = data.Immediately
                    ? triggerBuilder.WithSimpleSchedule(x => x.WithIntervalInSeconds(data.TimeSpan).RepeatForever().WithMisfireHandlingInstructionNowWithRemainingCount())
                    : triggerBuilder.WithSimpleSchedule(x => x.WithIntervalInSeconds(data.TimeSpan).RepeatForever().WithMisfireHandlingInstructionNextWithRemainingCount());
                    break;
                case Entity.Enum.JobType.Minutes:
                    //作业每...分钟执行一次
                    triggerBuilder = data.Immediately
                    ? triggerBuilder.WithSimpleSchedule(x => x.WithIntervalInMinutes(data.TimeSpan).RepeatForever().WithMisfireHandlingInstructionNowWithRemainingCount())
                    : triggerBuilder.WithSimpleSchedule(x => x.WithIntervalInMinutes(data.TimeSpan).RepeatForever().WithMisfireHandlingInstructionNextWithRemainingCount());
                    break;
                case Entity.Enum.JobType.Hours:
                    //作业每...小时执行一次
                    triggerBuilder = data.Immediately
                    ? triggerBuilder.WithSimpleSchedule(x => x.WithIntervalInHours(data.TimeSpan).RepeatForever().WithMisfireHandlingInstructionNowWithRemainingCount())
                    : triggerBuilder.WithSimpleSchedule(x => x.WithIntervalInHours(data.TimeSpan).RepeatForever().WithMisfireHandlingInstructionNextWithRemainingCount());
                    break;
                case Entity.Enum.JobType.Days:
                    //作业每...天执行一次
                    ts = data.NextTime.AddDays(data.TimeSpan).Subtract(data.NextTime);
                    triggerBuilder = data.Immediately
                    ? triggerBuilder.WithSimpleSchedule(x => x.WithInterval(ts).RepeatForever().WithMisfireHandlingInstructionNowWithRemainingCount())
                    : triggerBuilder.WithSimpleSchedule(x => x.WithInterval(ts).RepeatForever().WithMisfireHandlingInstructionNextWithRemainingCount());
                    break;
                case Entity.Enum.JobType.Weeks:
                    //作业每...周执行一次
                    ts = data.NextTime.AddDays(7 * data.TimeSpan).Subtract(data.NextTime);
                    triggerBuilder = data.Immediately
                    ? triggerBuilder.WithSimpleSchedule(x => x.WithInterval(ts).RepeatForever().WithMisfireHandlingInstructionNowWithRemainingCount())
                    : triggerBuilder.WithSimpleSchedule(x => x.WithInterval(ts).RepeatForever().WithMisfireHandlingInstructionNextWithRemainingCount());
                    break;
                case Entity.Enum.JobType.Months:
                    //作业每...月执行一次
                    ts = data.NextTime.AddMonths(data.TimeSpan).Subtract(data.NextTime);
                    triggerBuilder = data.Immediately
                    ? triggerBuilder.WithSimpleSchedule(x => x.WithInterval(ts).RepeatForever().WithMisfireHandlingInstructionNowWithRemainingCount())
                    : triggerBuilder.WithSimpleSchedule(x => x.WithInterval(ts).RepeatForever().WithMisfireHandlingInstructionNextWithRemainingCount());
                    break;
                case Entity.Enum.JobType.Years:
                    //作业每...年执行一次
                    ts = data.NextTime.AddYears(data.TimeSpan).Subtract(data.NextTime);
                    triggerBuilder = data.Immediately
                    ? triggerBuilder.WithSimpleSchedule(x => x.WithInterval(ts).RepeatForever().WithMisfireHandlingInstructionNowWithRemainingCount())
                    : triggerBuilder.WithSimpleSchedule(x => x.WithInterval(ts).RepeatForever().WithMisfireHandlingInstructionNextWithRemainingCount());
                    break;
                case Entity.Enum.JobType.Advanced:
                    //高级任务，即以Cron表达式制定工作排程
                    if (data.Immediately)
                    {
                        triggerBuilder = triggerBuilder.WithCronSchedule(data.CronExpression, x => x.WithMisfireHandlingInstructionFireAndProceed());
                    }
                    else
                    {
                        triggerBuilder = triggerBuilder.WithCronSchedule(data.CronExpression, x => x.WithMisfireHandlingInstructionDoNothing());
                    }
                    break;
            }
            return triggerBuilder.Build();
        }

        /// <summary>
        /// 停止当前服务器的排程
        /// </summary>
        internal void Stop()
        {
            Logs.Loggers.Cronlogger.Info("------关闭各项定时任务------");
            List<Entity.SystemCronConfigData> list = dbContext.SystemCronConfigData.ToList();

            foreach (Entity.SystemCronConfigData item in list)
            {
                Stop(item);
            }
            Scheduler.Shutdown(false);
            Logs.Loggers.Cronlogger.Info("------定时任务关闭完成------");
        }

        /// <summary>
        /// 开启当前服务器的排程
        /// </summary>
        internal void Start()
        {
            Logs.Loggers.Cronlogger.Info("------启动各项定时任务------");
            List<Entity.SystemCronConfigData> list = dbContext.SystemCronConfigData.ToList();

            list.ForEach(x =>
            {
                if (x.IsValidity)
                {
                    Add(x);
                }
            });

            Scheduler.Start();
            Logs.Loggers.Cronlogger.Info("------定时任务启动完成------");
        }

        /// <summary>
        /// 新增当前服务器的任务
        /// </summary>
        /// <param name="job"></param>
        internal bool Add(Entity.SystemCronConfigData data)
        {
            try
            {
                ITrigger trigger;

                if (!data.IsValidity)
                {
                    Stop(data);
                    Logs.Loggers.Cronlogger.Info(string.Format("任务{0}({1}/{2})已终止。", data.JobName, data.GroupId, data.JobKey));
                    return false;
                }
                if (string.IsNullOrWhiteSpace(data.Assembly))
                {
                    Logs.Loggers.Cronlogger.Error(string.Format("定时任务{0}没有配置可运行的类{1}。", data.JobName, data.Assembly));
                    return false;
                }

                System.Type type = System.Type.GetType(data.Assembly);
                if (type == null)
                {
                    dbContext.SystemCronConfigData.Remove(data);
                    dbContext.SaveChanges();
                    Logs.Loggers.Cronlogger.Error(string.Format("定时任务{0}没有配置可运行的类{1}，任务配置已删除。", data.JobName, data.Assembly));
                    return false;
                }

                if (Array.IndexOf(type.GetInterfaces(), typeof(IScheduledTask)) == -1)
                {
                    Logs.Loggers.Cronlogger.Error(string.Format("定时任务{0}没有正确继承接口TaskManagementSystem.Middleware.IScheduledTask。", data.JobName));
                    return false;
                }

                if ((Entity.Enum.JobType)data.JobType == Entity.Enum.JobType.Advanced && string.IsNullOrWhiteSpace(data.CronExpression))
                {
                    Logs.Loggers.Cronlogger.Error(string.Format("定时任务{0}指定以Cron表达式制定工作排程，表达式CronExpression却为空。", data.JobName));
                    return false;
                }
                
                JobKey jobKey = GetJobKey(data.JobKey, data.GroupId.ToString());
                IJobDetail jobDetail = Scheduler.GetJobDetail(jobKey);
                TriggerKey tk = GetTriggerKey(data.JobKey, data.GroupId.ToString());

                if (jobDetail != null)
                {
                    RemoveTrigger(tk);
                    Scheduler.DeleteJob(jobKey);
                }

                jobDetail = JobBuilder.Create(typeof(ScheduledTaskExecuteBase)).WithIdentity(jobKey).Build();

                DateTimeOffset? ft = new DateTimeOffset();
                trigger = SetJobTrigger(data);
                ft = Scheduler.ScheduleJob(jobDetail, trigger);
                Logs.Loggers.Cronlogger.Info(string.Format("定时任务[{0}]将在{1}开始运行[{2}]。", data.JobName, ft.GetValueOrDefault().DateTime, data.Assembly));
            }
            catch (Exception ex)
            {
                Logs.Loggers.Cronlogger.Error(ex, string.Format("定时任务[{0}({1}/{2})]新增发生错误。", data.JobName, data.GroupId.ToString(), data.JobKey));
                return false;
            }

            return true;
        }

        internal static TriggerKey GetTriggerKey(string jobKey, string jobGroup)
        {
            string name = string.Format("trigger_{0}_{1}", jobGroup, jobKey);
            TriggerKey tk = new TriggerKey(name, jobGroup);
            return tk;
        }

        internal static JobKey GetJobKey(string jobKey, string jobGroup)
        {
            return JobKey.Create(jobKey, jobGroup);
        }

        internal static bool TryGetKeyAndGroup(JobKey jobKey, out string key, out string group)
        {
            try
            {
                group = jobKey.ToString().Split('.')[0];
                key = jobKey.ToString().Split('.')[1];
            }
            catch (Exception ex)
            {
                group = string.Empty;
                key = string.Empty;
                return false;
            }
            return true;
        }

        /// <summary>
        /// 停止当前服务器的任务
        /// </summary>
        /// <param name="job"></param>
        internal bool Stop(Entity.SystemCronConfigData data)
        {
            try
            {
                TriggerKey tk = GetTriggerKey(data.JobKey, data.GroupId.ToString());
                JobKey jk = GetJobKey(data.JobKey, data.GroupId.ToString());
                RemoveTrigger(tk);
                Scheduler.DeleteJob(jk);
                data.IsRunning = false;
                dbContext.Entry(data).State = System.Data.Entity.EntityState.Modified;
                dbContext.SaveChanges();
                Logs.Loggers.Cronlogger.Info(string.Format("定时任务[{0}({1}/{2})]被移除。", data.JobName, data.GroupId, data.JobKey));
                return true;
            }
            catch (Exception ex)
            {
                Logs.Loggers.Cronlogger.Error(ex, string.Format("定时任务[{0}({1}/{2})]移除失败", data.JobName, data.GroupId, data.JobKey));
                return false;
            }
        }

        /// <summary>
        /// 移除触发器
        /// </summary>
        /// <param name="tk"></param>
        /// <returns></returns>
        private bool RemoveTrigger(TriggerKey tk)
        {
            try
            {
                Scheduler.PauseTrigger(tk);// 停止触发器
                return Scheduler.UnscheduleJob(tk);// 移除触发器 
            }
            catch (SchedulerException ex)
            {
                Logs.Loggers.Cronlogger.Error(ex);
                return false;
            }
        }
    }
}